# ~*~ coding: utf-8 ~*~
import json
import re
import time

from flask import current_app

from apps.extensions import db, celery
from apps.ops.utils import update_or_create_ansible_task
from apps.common.utils import encrypt_password
from . import const
from .utils.extract import (
    capacity_convert, sum_capacity, ip_address_discern
)
from .utils.ansible import db_to_ansible_of_asset_admin_users
from .utils.connectivity import Connectivity
from .models import Asset
from apps.ops.celery.decorator import register_as_period_task

__all__ = [
    'update_asset_hardware_info_manual', 'test_assets_user_connectivity_manual',
    'push_system_users_to_assets', 'push_system_user_to_asset',
    'delete_system_users_to_assets', 'delete_system_user_to_asset'
]


# #####  Update asset hardware info tasks #####

def set_assets_hardware_info(assets, result, **kwargs):
    """
    Using ops task run result, to update asset info

    @shared_task must be exit, because we using it as a task callback, is must
    be a celery task also
    :param assets:
    :param result:
    :param kwargs: {task_name: ""}
    :return:
    """
    result_raw = result[0]
    assets_updated = []
    success_result = result_raw.get('ok', {})

    for asset in assets:
        hostname = asset.hostname
        info = success_result.get(hostname, {})
        info = info.get('setup', {}).get('ansible_facts', {})
        if not info:
            continue
        ___vendor = info.get('ansible_system_vendor', 'Unknown')
        ___model = info.get('ansible_product_name', 'Unknown')
        ___sn = info.get('ansible_product_serial', 'Unknown')

        for ___cpu_model in info.get('ansible_processor', []):
            if ___cpu_model.endswith('GHz') or ___cpu_model.startswith("Intel"):
                break
        else:
            ___cpu_model = 'Unknown'
        ___cpu_model = ___cpu_model[:64]
        ___cpu_count = info.get('ansible_processor_count', 0)
        ___cpu_cores = info.get('ansible_processor_cores', None) or \
                       len(info.get('ansible_processor', []))
        ___cpu_vcpus = info.get('ansible_processor_vcpus', 0)
        ___memory = '%s %s' % capacity_convert(
            '{} MB'.format(info.get('ansible_memtotal_mb'))
        )
        disk_info = {}
        for dev, dev_info in info.get('ansible_devices', {}).items():
            disk_pattern = re.compile(r'^hd|sd|xvd|vd')
            if disk_pattern.match(dev) and dev_info['removable'] == '0':
                disk_info[dev] = dev_info['size']
        ___disk_total = '%s %s' % sum_capacity(disk_info.values())
        ___disk_info = json.dumps(disk_info)

        ___platform = info.get('ansible_system', 'Unknown')
        ___os = info.get('ansible_distribution', 'Unknown')
        ___os_version = info.get('ansible_distribution_version', 'Unknown')
        ___os_arch = info.get('ansible_architecture', 'Unknown')
        ___public_ip = ','.join(ip_address_discern(info.get('ansible_all_ipv4_addresses', 'Unknown'))[1])
        ___hostname_raw = info.get('ansible_hostname', 'Unknown')
        update_fields = {}
        for k, v in locals().items():
            if k.startswith('___'):
                update_fields[k.strip('_')] = v
        update_fields['update_time'] = time.time()
        asset.update(**update_fields)
        assets_updated.append(asset)
    return assets_updated


def update_assets_hardware_info_util(assets, task_name=None):
    """
    Using ansible api to update asset hardware info
    :param assets:  asset object
    :param task_name: task_name running
    :return: result summary ['contacted': {}, 'dark': {}]
    """
    tasks = const.UPDATE_ASSETS_HARDWARE_TASKS

    ansible_run_info = db_to_ansible_of_asset_admin_users(assets)
    if not ansible_run_info:
        current_app.logger.info('无法更新硬件资产信息')
        return

    hosts, run_as, become = ansible_run_info
    if hosts and run_as:
        task = update_or_create_ansible_task(task_name=task_name, create_by='system', hosts=hosts,
                                             tasks=tasks, pattern='all', options={},
                                             run_as=run_as, become=become, run_as_admin=True)
        result = task.run()
        set_assets_hardware_info(assets, result)
        return result


@celery.task
def update_asset_hardware_info_manual(assets):
    if isinstance(assets, list):
        assets = [db.session.merge(asset) for asset in assets]
    else:
        assets = [db.session.merge(assets)]

    hosts = [asset.hostname for asset in assets]
    update_assets_hardware_info_util(assets, task_name="更新资产硬件信息：{}".format(', '.join(hosts)))


@celery.task
@register_as_period_task(name="update-assets-hardware-info", crontab='*/30 * * * *')
def update_asset_hardware_info_periodicity():
    assets = db.session.query(Asset).all()
    update_assets_hardware_info_util(assets, task_name="更新资产硬件信息：所有资产")

# #####  Test Asset user connectivity task #####


def set_assets_connectivity(assets, result):
    result_summary = result[1]
    unreachable = result_summary.get('dark', {}).keys()
    reachable = result_summary.get('contacted', {}).keys()

    for asset in assets:
        if asset.hostname in unreachable:
            print('In Unreachable')
            asset.connectivity = Connectivity.unreachable()
        elif asset.hostname in reachable:
            print('In Reachable')
            asset.connectivity = Connectivity.reachable()
        else:
            print('In Unknown')
            asset.connectivity = Connectivity.unknown()


def test_assets_user_connectivity_util(assets, task_name):
    tasks = const.TEST_ASSET_USER_CONN_TASKS

    ansible_run_info = db_to_ansible_of_asset_admin_users(assets)
    if not ansible_run_info:
        current_app.logger.info('无法测试资产的连接性')
        return

    hosts, run_as, become = ansible_run_info
    task = update_or_create_ansible_task(task_name=task_name, create_by='system', hosts=hosts,
                                         tasks=tasks, pattern='all', options={},
                                         run_as=run_as, become=become, run_as_admin=False)
    result = task.run()
    set_assets_connectivity(assets, result)


@celery.task
def test_assets_user_connectivity_manual(assets):
    """
    测试资产的连接性
    :param assets: 资产的游离态对象
    :return:
    """
    if isinstance(assets, list):
        assets = [db.session.merge(asset) for asset in assets]
    else:
        assets = [db.session.merge(assets)]

    hosts = [asset.hostname for asset in assets]
    test_assets_user_connectivity_util(assets, "测试资产的连接性：{}".format(', '.join(hosts)))


@celery.task
@register_as_period_task(name="test-assets-user-connectivity", crontab='*/30 * * * *')
def test_assets_user_connectivity_periodicity():
    assets = db.session.query(Asset).all()
    test_assets_user_connectivity_util(assets, "测试资产的连接性：所有资产")


# #####  Push system user tasks #####

def get_push_system_user_tasks(system_user):
    if not system_user.is_need_push():
        msg = "Push system user task skip, auto push not enable or protocol is not ssh: {}".format(system_user.name)
        current_app.logger.debug(msg)
        return []

    # Set root as system user is dangerous
    if system_user.username == "root":
        return []

    tasks = []
    # 创建系统用户
    if system_user.password:
        tasks.append({
            'name': 'Add user {}'.format(system_user.username),
            'action': {
                'module': 'user',
                'args': 'name={} shell={} state=present password={}'.format(
                    system_user.username, system_user.shell,
                    # '$6$K3mIlKK$20JcYLm3YjQ12KSvbLro/zedKbwgckB7vjiKMRj8OeoaqahF56tAtAUILCM2qzu8olCkKDgkN23LR11RKdyAC/'
                    encrypt_password(system_user.password, salt="K3mIlKK"),
                ),
            }
        })
        tasks.extend([
            {
                'name': 'Check home dir exists',
                'action': {
                    'module': 'stat',
                    'args': 'path=/home/{}'.format(system_user.username)
                },
                'register': 'home_existed'
            },
            {
                'name': "Set home dir permission",
                'action': {
                    'module': 'file',
                    'args': "path=/home/{0} owner={0} group={0} mode=700".format(system_user.username)
                },
                'when': 'home_existed.stat.exists == true'
            }
        ])
    # 上传公钥到服务器
    if system_user.public_key:
        tasks.append({
            'name': 'Set {} authorized key'.format(system_user.username),
            'action': {
                'module': 'authorized_key',
                'args': "user={} state=present key='{}'".format(
                    system_user.username, system_user.public_key
                )
            }
        })
    # 配置用户sudo权限
    if system_user.sudo:
        tasks.append({
            'name': 'Set {} sudo setting'.format(system_user.username),
            'action': {
                'module': 'lineinfile',
                'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
                        "line='{0} ALL=(ALL) NOPASSWD: {1}' "
                        "validate='visudo -cf %s'".format(system_user.username, system_user.sudo)
            }
        })
    return tasks


def push_system_user_util(system_users, assets, task_name=None):
    tasks = []
    for system_user in system_users:
        tasks.extend(get_push_system_user_tasks(system_user))

    task_name = '推送系统用户到资产'
    hosts, run_as, become = db_to_ansible_of_asset_admin_users(assets)
    if not hosts or not run_as or not become:
        return
    task = update_or_create_ansible_task(task_name=task_name, create_by='system', hosts=hosts,
                                         tasks=tasks, pattern='all', options={},
                                         run_as=run_as, become=become, run_as_admin=True)
    return task.run()


@celery.task
def push_system_user_to_asset(system_user, asset):
    system_user = db.session.merge(system_user)
    asset = db.session.merge(asset)
    if system_user and asset:
        return push_system_user_util([system_user], [asset])


@celery.task
def push_system_users_to_assets(system_users, assets):
    system_users = [db.session.merge(system_user) for system_user in system_users]
    assets = [db.session.merge(asset) for asset in assets]
    if system_users and assets:
        return push_system_user_util(system_users, assets)


# #####  Delete system user tasks #####

def get_delete_system_user_tasks(system_user):
    if not system_user.is_need_push():
        msg = "Delete system user task skip, auto push not enable or protocol is not ssh: {}".format(system_user.name)
        current_app.logger.log(msg)
        return []

    # Set root as system user is dangerous
    if system_user.username == "root":
        return []

    tasks = []
    # 删除系统用户
    if system_user.username:
        tasks.append({
            'name': 'Delete user {}'.format(system_user.username),
            'action': {
                'module': 'user',
                'args': 'name={} state=absent remove=yes force=true'.format(
                    system_user.username, system_user.shell,
                ),
            }
        })

    # 删除服务器上改系统用户的公钥
    # if system_user.public_key:
    #     tasks.append({
    #         'name': 'Delete {} authorized key'.format(system_user.username),
    #         'action': {
    #             'module': 'authorized_key',
    #             'args': "user={} state=absent key='{}'".format(
    #                 system_user.username, system_user.public_key
    #             )
    #         }
    #     })

    # 配置用户sudo权限
    if system_user.sudo:
        tasks.append({
            'name': 'Delete {} sudo setting'.format(system_user.username),
            'action': {
                'module': 'lineinfile',
                'args': "dest=/etc/sudoers state=absent "
                        "line='{0} ALL=(ALL) NOPASSWD: {1}' "
                        "validate='visudo -cf %s'".format(system_user.username, system_user.sudo)
            }
        })
    return tasks


def delete_system_user_util(system_users, assets, task_name=None):
    tasks = []
    for system_user in system_users:
        tasks.extend(get_delete_system_user_tasks(system_user))
    task_name = '删除资产中系统用户'
    hosts, run_as, become = db_to_ansible_of_asset_admin_users(assets)
    if not hosts or not run_as or not become:
        return

    task = update_or_create_ansible_task(task_name=task_name, create_by='system', hosts=hosts,
                                         tasks=tasks, pattern='all', options={},
                                         run_as=run_as, become=become, run_as_admin=True)
    return task.run()


@celery.task
def delete_system_user_to_asset(system_user, asset):
    system_user = db.session.merge(system_user)
    asset = db.session.merge(asset)
    if system_user and asset:
        return delete_system_user_util([system_user], [asset])


@celery.task
def delete_system_users_to_assets(system_users, assets):
    system_users = [db.session.merge(system_user) for system_user in system_users]
    assets = [db.session.merge(asset) for asset in assets]
    if system_users and assets:
        return delete_system_user_util(system_users, assets)

